Android 测量,布局,绘制流程解析

Android 测量,布局,绘制流程解析

前言

Android 中自定义 View 常常需要覆写 onMeasure, onDraw 方法,而自定义 ViewGroup 则还需要覆写 onLayout 方法,那么这三者之间到底是如何调用的呢

源码解析基于 Android 7.1

测量,布局,绘制流程解析

触发测量布局绘制的流程是在 ViewRootImpl 中的 performTraversals 方法中,追溯到源头是由 Choreographer 类的帧回调触发的,这个方法中的代码非常多,所以在以下解析中会省略部分的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
private void performTraversals() {

...

mIsInTraversal = true;

... 省略部分代码

// 这边的判断状态非常多,如果窗口属性,大小,显示区域发生了改变,或者是人为 requestLayout 都会触发重新测量
if (mFirst || windowShouldResize || insetsChanged ||
viewVisibilityChanged || params != null || mForceNextWindowRelayout) {

... 省略部分代码

if (!mStopped || mReportNextDraw) {
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
updatedConfiguration) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

if (DEBUG_LAYOUT) Log.v(mTag, "Ooops, something changed! mWidth="
+ mWidth + " measuredWidth=" + host.getMeasuredWidth()
+ " mHeight=" + mHeight
+ " measuredHeight=" + host.getMeasuredHeight()
+ " coveredInsetsChanged=" + contentInsetsChanged);

// 触发的是 DecorView 的 measure 方法,开始沿着 ViewTree 往下递归测量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

// 如果有 weight 属性,会再次测量一遍
int width = host.getMeasuredWidth();
int height = host.getMeasuredHeight();
boolean measureAgain = false;

if (lp.horizontalWeight > 0.0f) {
width += (int) ((mWidth - width) * lp.horizontalWeight);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (lp.verticalWeight > 0.0f) {
height += (int) ((mHeight - height) * lp.verticalWeight);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
MeasureSpec.EXACTLY);
measureAgain = true;
}

if (measureAgain) {
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}

layoutRequested = true;
}
}
}
// 这边是测量相关的出发代码,DecorView 作为根节点沿着 view tree 向下测量

... 省略部分代码

// 这个逻辑可以看出,如果重新测量,那么一个会发生重新布局
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
boolean triggerGlobalLayoutListener = didLayout
|| mAttachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
// 触发的是 DecorView 的 layout 方法,开始沿着 ViewTree 往下布局
performLayout(lp, mWidth, mHeight);

// 这个点已经完成了测量和布局,可以计算透明的地区了

if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {

... 省略部分代码

}
}

// 这里有 onGlobalLayout 的回调,所以获取 view 的宽高可以在这个回调里进行,因为已经进行过测量流程了
if (triggerGlobalLayoutListener) {
mAttachInfo.mRecomputeGlobalAttributes = false;
mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
}

... 省略部分代码

boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;

// 这里会做一些前置判断,一般情况下这个判断是成立的
if (!cancelDraw && !newSurface) {
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}
// 最终触发的是 DecorView 的 draw 方法,开始沿着 ViewTree 往下绘制
performDraw();
} else {
if (isViewVisible) {
// 如果是正在创建一个新 newSurface,会延迟此次 draw,重新走一遍上述流程一遍一次性全部绘制出来
scheduleTraversals();
} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).endChangingAnimations();
}
mPendingTransitions.clear();
}
}

mIsInTraversal = false;
}

最终都是通过 mView 即 DecorView 这个真正意义上的根 view 开始测量布局绘制的流程分发

ViewRootImpl触发流程

measure

View.measure 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int oWidth = insets.left + insets.right;
int oHeight = insets.top + insets.bottom;
widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth);
heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
}

// 缓存,对不同的 MeasureSpec 会对测量结果进行缓存设置
long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);

// 这是强制重新测量的标志
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;

// 通过一系列判断来确定是否需要重新测量
// 当 MeasureSpec 改变时,布局模式不是确定尺寸或者给定的尺寸发生了变化都会触发重测量
final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
|| heightMeasureSpec != mOldHeightMeasureSpec;
final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
&& MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
&& getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
final boolean needsLayout = specChanged
&& (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);

if (forceLayout || needsLayout) {
// 清除该标记位是为了后面通过对该标记位检测判断是否调用的 setMeasuredDimension 方法
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

resolveRtlPropertiesIfNeeded();
// 这里边的逻辑会读取缓存测量
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// 这里会调用 onMeasure,也就是我们需要自定义逻辑的地方
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
// 由于跳过了 onMeasure,所以设置了标志位,会在 layout 时调用 onMeasure
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}

// 这里会判断是否调用了 setMeasuredDimension 方法,防止自定义忘记调用改方法
if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
throw new IllegalStateException("View with id " + getId() + ": "
+ getClass().getName() + "#onMeasure() did not set the"
+ " measured dimension by calling"
+ " setMeasuredDimension()");
}
// 测量完毕强制标记 PFLAG_LAYOUT_REQUIRED 用于 layout,尺寸改变必定要重新布局
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}

// 记录当前的状态待下次判断
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;

mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}

由于 ViewGroup 中并没有覆写 measure 方法,所以在 ViewGroup 中调用子 view 的 measure 方法需要在 onMeasure 中自己完成了。
ViewGroup 中有提供 measureChildren 测量所有的子 View,measureChild 及 measureChildWithMargins 测量单个子 View 的方法,需要自定义 GroupView 时自己选择调用,千万不能忘记,还有要注意的是 View 的 visible 状态为 Gone 时应该跳过测量。系统的 ViewGroup 中都根据自己的需求实现了 onMeasure 方法,有时会测量多次,比如 RelativeLayout 需要横竖测量两次,LinearLayout 在有 weight 属性时也需要测量两次。在调用子 View 的 measure 后,就可以通过 getMeasureXX 获取测量结果了。

layout

View.layout 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public void layout(int l, int t, int r, int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
// measure 过程中跳过 onMeasure 步骤会在这里被调用
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}

int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;

// 判断 layout 的范围是否改变,同时也完成了显示范围的设置
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
// 调用了 onLayout 方法,可以自定义逻辑,View 中是一个空实现,而 ViewGroup 则是一个抽象方法
onLayout(changed, l, t, r, b);

if (shouldDrawRoundScrollbar()) {
if(mRoundScrollbarRenderer == null) {
mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
}
} else {
mRoundScrollbarRenderer = null;
}

// 清除该标记
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

// 接口回调
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (int i = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}

// 清除这个强制测量布局的标志位
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
// 自动填充的触发时机在布局完成之后
if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
notifyEnterOrExitForAutoFillIfNeeded(true);
}
}

onLayout 是 ViewGroup 的属性,我们需要实现自己的逻辑去完成子 View 在 ViewGroup 中的布局,调用的是子 View 的 layout 方法

draw

View.draw 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
// 这个状态说明此控件的绘制区域会被其他绘制完全遮盖住,既没有透明度,也没有空缺
// 一般这个标记取决于子 View,在 invalidate 中有相关设置,而且是唯一的设置方式
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
// flag 清除 dirty 标记后打上 PFLAG_DRAWN 标记用来表示已经被绘制过了
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/

// Step 1, 绘制区域可见,需要绘制背景
int saveCount;

if (!dirtyOpaque) {
drawBackground(canvas);
}

// 尽可能地跳过步骤 Step 2 和 Step 5,通常情况也是跳过的
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
// 如果禁止了渐隐边界,走下面的逻辑,这是一般情况,所以是没有 Step 2 和 Step 5 的
if (!verticalEdges && !horizontalEdges) {
// Step 3, 绘制区域可见, 绘制内容,这是我们自定实现的 onDraw 逻辑
if (!dirtyOpaque) onDraw(canvas);

// Step 4, 绘制子 view
dispatchDraw(canvas);

drawAutofilledHighlight(canvas);

// Overlay 是内容的一部分,所以绘制于 Foreground 下方
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}

// Step 6, 绘制前面板装饰内容,比如说滚动条
onDrawForeground(canvas);

// Step 7, 绘制默认的高亮场景
drawDefaultFocusHighlight(canvas);

if (debugDraw()) {
debugDrawFocus(canvas);
}

return;
}

... 省略了FADING_EDGE相关处理,不常用

}

单纯的 draw 方法逻辑比较简单,继续追踪 dispatchDraw 方法,View 中的 dispatchDraw 是一个空方法,所以主要逻辑在 ViewGroup 中的 dispatchDraw。由于这里面的方法比较复杂,所以我们就不贴出来了,需要知道的是 dispatchDraw 里面会遍历子 view,调用的是 boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) 方法,而不是 draw(Canvas canvas)。

View.draw(canvas, parent, drawingTime) 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {

...略去部分代码,我们来看分发的关键代码

if (!drawingWithDrawingCache) {
if (drawingWithRenderNode) {
// 硬件加速
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
((DisplayListCanvas) canvas).drawRenderNode(renderNode);
} else {
// ViewGroup 有个特性是如果没有 backgroud 就会直接分发,可以通过 setWillNotDraw(false) 来跳过
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchDraw(canvas);
} else {
// 这就是上面的方法
draw(canvas);
}
}
} else if (cache != null) {
// 有缓存的则画出缓存图片即可
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
if (layerType == LAYER_TYPE_NONE || mLayerPaint == null) {
// no layer paint, use temporary paint to draw bitmap
Paint cachePaint = parent.mCachePaint;
if (cachePaint == null) {
cachePaint = new Paint();
cachePaint.setDither(false);
parent.mCachePaint = cachePaint;
}
cachePaint.setAlpha((int) (alpha * 255));
canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);
} else {
// use layer paint to draw the bitmap, merging the two alphas, but also restore
int layerPaintAlpha = mLayerPaint.getAlpha();
if (alpha < 1) {
mLayerPaint.setAlpha((int) (alpha * layerPaintAlpha));
}
canvas.drawBitmap(cache, 0.0f, 0.0f, mLayerPaint);
if (alpha < 1) {
mLayerPaint.setAlpha(layerPaintAlpha);
}
}
}

...
}

就这样一直分发到最底层的 view 完成了绘制过程

requestLayout() & invalidate()

View.requestLayout 会触发 view 的测量布局刷新,而 invalidate 只会触发 view 的绘制

View.requestLayout 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public void requestLayout() {
// 清除缓存的测量结果
if (mMeasureCache != null) mMeasureCache.clear();

if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
// 这里会标记请求布局的界面,如果发现 ViewRoot 正在布局将此次的 request 暂存,ViewRootImpl 的 performLayout 有相关读取操作
ViewRootImpl viewRoot = getViewRootImpl();
if (viewRoot != null && viewRoot.isInLayout()) {
if (!viewRoot.requestLayoutDuringLayout(this)) {
return;
}
}
// 标记发起的 view 防止往上调用传递的时候多次标记发起的 view
mAttachInfo.mViewRequestingLayout = this;
}

// 将必须的标志位标记上
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;

if (mParent != null && !mParent.isLayoutRequested()) {
// 通过 View tree 往上传递直至 ViewRootImpl 中 requestLayout 最终还是会触发 performTraversals 方法
// 所有 view 的调用链上的 view 都必须打上标记,这样 ViewRootImpl 发起测量布局绘制时才能沿着树链往下传递调用
mParent.requestLayout();
}
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
mAttachInfo.mViewRequestingLayout = null;
}
}

由于 requestLayout 方法会触发 view 所在的整条链上的调用 view 去重新测量布局绘制,所以尽量避免频繁的调用该方法,比如说尽量固定 view 的大小,通过 view 的 layout 方法更改位置,在大小固定的情况下自定义 View 可以对调用 requestLayout 方法做一些限制

View.invalidate 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/**
* invalidate() 最终调用的是该方法
* 传参 invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, true, true)
*/
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {

if (mGhostView != null) {
mGhostView.invalidate(true);
return;
}

// 有些情况会跳过绘制,比如说控件不可见,控件正在处理动画,或者是 DecorView
if (skipInvalidate()) {
return;
}

if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
|| (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
|| (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED
|| (fullInvalidate && isOpaque() != mLastIsOpaque)) {
if (fullInvalidate) {
mLastIsOpaque = isOpaque();
mPrivateFlags &= ~PFLAG_DRAWN;
}

mPrivateFlags |= PFLAG_DIRTY;

if (invalidateCache) {
mPrivateFlags |= PFLAG_INVALIDATED;
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
}

// 将更改的区域即需要绘制的区域传给父控件的 invalidateChild 处理
final AttachInfo ai = mAttachInfo;
final ViewParent p = mParent;
if (p != null && ai != null && l < r && t < b) {
final Rect damage = ai.mTmpInvalRect;
damage.set(l, t, r, b);
p.invalidateChild(this, damage);
}

// 如果背景是父控件投射出来的,而不是 view 自带的,那么清理掉
if (mBackground != null && mBackground.isProjected()) {
final View receiver = getProjectionReceiver();
if (receiver != null) {
receiver.damageInParent();
}
}
}
}

View 的 invalidateInternal 会传入绘制区域,在这个方法中会调用父 ViewGroup 的 invalidateChild 的方法

ViewGroup.invalidateChild 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
public final void invalidateChild(View child, final Rect dirty) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null && attachInfo.mHardwareAccelerated) {
// HW accelerated fast path
onDescendantInvalidated(child, child);
return;
}

ViewParent parent = this;
if (attachInfo != null) {

final boolean drawAnimation = (child.mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0;

// 这里要确认是否发起重绘的view是否是个不透明的view
Matrix childMatrix = child.getMatrix();
// child.isOpaque 方法读取的是 PFLAG_OPAQUE_MASK 和 View 透明度
// 而 PFLAG_OPAQUE_MASK 是在 computeOpaqueFlags 方法中由 PFLAG_OPAQUE_BACKGROUND 和 PFLAG_OPAQUE_SCROLLBARS 标志位合成的
final boolean isOpaque = child.isOpaque() && !drawAnimation &&
child.getAnimation() == null && childMatrix.isIdentity();
// 根据透明或者不透明,得到绘制的标志位
int opaqueFlag = isOpaque ? PFLAG_DIRTY_OPAQUE : PFLAG_DIRTY;

... 省去一些其他标志位操作和 dirty 区域变换的结果,因为 dirty 是一个相对坐标系

// 这里通过一个循环不断向上传递,知道 ViewRootImpl
do {
View view = null;
if (parent instanceof View) {
view = (View) parent;
}

// 处理动画时间
if (drawAnimation) {
if (view != null) {
view.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
} else if (parent instanceof ViewRootImpl) {
((ViewRootImpl) parent).mIsAnimating = true;
}
}

// 该方法会为所有的父 view 打上 PFLAG_DIRTY_MASK 绘制标记
// 会决定绘制是父 View 是否会跳过 drawBackgroud 和 onDraw 方法
if (view != null) {
if ((view.mViewFlags & FADING_EDGE_MASK) != 0 &&
view.getSolidColor() == 0) {
opaqueFlag = PFLAG_DIRTY;
}
if ((view.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) {
view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | opaqueFlag;
}
}

// view tree 上的整条链都会调用这个方法,直到 ViewRootImpl 的 invalidateChildInParent 去触发 performTraversals,这个方法会调整 dirty 到父 View 的大小
parent = parent.invalidateChildInParent(location, dirty);
if (view != null) {
// Account for transform on current parent
Matrix m = view.getMatrix();
if (!m.isIdentity()) {
RectF boundingRect = attachInfo.mTmpTransformRect;
boundingRect.set(dirty);
m.mapRect(boundingRect);
dirty.set((int) Math.floor(boundingRect.left),
(int) Math.floor(boundingRect.top),
(int) Math.ceil(boundingRect.right),
(int) Math.ceil(boundingRect.bottom));
}
}
} while (parent != null);
}
}

而在 ViewRootImpl.invalidateChildInParent 会把 dirty 最终范围带入在触发 draw 的时候则会锁定这个画布的范围更新视图

invalidate 也是验证树层层往上传递处理标记标志位,最后由 ViewRootImpl 触发整个树的刷新,只不过由于没有标记测量和布局的标志位,所以不会触发重测量和布局

结语

其实 Android 中关于布局的测量布局和绘制是一个非常复杂的流程,由 ViewRootImpl 发起遍历树的操作,而 ViewGroup 和 View 中有各种各样的标志位来标记操作的执行。这篇文章也只是讲了其中的一小部分,也仅仅是记录了一下我的理解。